testtreelistmodel: Make the directory loading async
authorBenjamin Otte <otte@redhat.com>
Wed, 12 Sep 2018 03:04:17 +0000 (05:04 +0200)
committerBenjamin Otte <otte@redhat.com>
Sun, 16 Sep 2018 16:50:17 +0000 (18:50 +0200)
This is way more complicated than it should be, because it requires
manually limiting the number of open file enumerators.

On the other hand, it exhaustively tests the items-changed emission of
all involved listmodels because those signals come in pretty much
randomly.

It's also 50% slower than the sync version, with the caeat that the sync
version only shows the UI after it's done loading, while this version
shows it right away.

tests/testtreelistmodel.c

index 6b3fa7a9c971f234da6857c0455e6a11d594d74d..42ec20a3b27a8fe391c2718cef3a5acd7fa058a9 100644 (file)
 
 #define ROWS 30
 
-static GListModel *
-create_list_model_for_directory (gpointer file,
-                                 gpointer unused)
+GSList *pending;
+guint active = 0;
+
+static void
+got_files (GObject      *enumerate,
+           GAsyncResult *res,
+           gpointer      store);
+
+static gboolean
+start_enumerate (GListStore *store)
 {
   GFileEnumerator *enumerate;
-  GListStore *store;
-  GFile *child;
-  GFileInfo *info;
-
-  if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
-    return NULL;
+  GFile *file = g_object_get_data (G_OBJECT (store), "file");
+  GError *error = NULL;
 
   enumerate = g_file_enumerate_children (file,
                                          G_FILE_ATTRIBUTE_STANDARD_TYPE
                                          "," G_FILE_ATTRIBUTE_STANDARD_NAME,
                                          0,
                                          NULL,
-                                         NULL);
+                                         &error);
+
   if (enumerate == NULL)
-    return NULL;
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES))
+        {
+          g_clear_error (&error);
+          pending = g_slist_prepend (pending, g_object_ref (store));
+          return TRUE;
+        }
+
+      g_clear_error (&error);
+      g_object_unref (store);
+      return FALSE;
+    }
 
-  store = g_list_store_new (G_TYPE_FILE);
+  if (active > 20)
+    {
+      g_object_unref (enumerate);
+      pending = g_slist_prepend (pending, g_object_ref (store));
+      return TRUE;
+    }
 
-  while (g_file_enumerator_iterate (enumerate, &info, NULL, NULL, NULL))
+  active++;
+  g_file_enumerator_next_files_async (enumerate,
+                                      g_file_is_native (file) ? 5000 : 100,
+                                      G_PRIORITY_DEFAULT_IDLE,
+                                      NULL,
+                                      got_files,
+                                      g_object_ref (store));
+
+  g_object_unref (enumerate);
+  return TRUE;
+}
+
+static void
+got_files (GObject      *enumerate,
+           GAsyncResult *res,
+           gpointer      store)
+{
+  GList *l, *files;
+  GFile *file = g_object_get_data (store, "file");
+  GPtrArray *array;
+
+  files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL);
+  if (files == NULL)
+    {
+      g_object_unref (store);
+      if (pending)
+        {
+          GListStore *store = pending->data;
+          pending = g_slist_remove (pending, store);
+          start_enumerate (store);
+        }
+      active--;
+      return;
+    }
+
+  array = g_ptr_array_new ();
+  g_ptr_array_new_with_free_func (g_object_unref);
+  for (l = files; l; l = l->next)
     {
-      if (info == NULL)
-        break;
+      GFileInfo *info = l->data;
+      GFile *child;
 
       child = g_file_get_child (file, g_file_info_get_name (info));
-      g_list_store_append (store, child);
-      g_object_unref (child);
+      g_ptr_array_add (array, child);
     }
+  g_list_free_full (files, g_object_unref);
 
-  g_object_unref (enumerate);
+  g_list_store_splice (store, g_list_model_get_n_items (store), 0, array->pdata, array->len);
+  g_ptr_array_unref (array);
 
-  return G_LIST_MODEL (store);
+  g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate),
+                                      g_file_is_native (file) ? 5000 : 100,
+                                      G_PRIORITY_DEFAULT_IDLE,
+                                      NULL,
+                                      got_files,
+                                      store);
+}
+
+static GListModel *
+create_list_model_for_directory (gpointer file,
+                                 gpointer unused)
+{
+  GListStore *store;
+
+  if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
+    return NULL;
+
+  store = g_list_store_new (G_TYPE_FILE);
+  g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref);
+
+  if (start_enumerate (store))
+    return G_LIST_MODEL (store);
+  else
+    return NULL;
 }
 
 static GtkWidget *